import * as Bluebird from 'bluebird'
import * as redis from 'redis';
import Club from "../database/models/club";
import config from '../utils/config'
import {RoomRegister} from "./RoomRegister";
import {TournamentRoom, BattleRoom} from "./doudizhu/TournamentRoom";

Bluebird.promisifyAll(redis.RedisClient.prototype)

/**
 *
 * @param gameName
 * @param roomFactory
 * @param roomFee
 * @returns { {new(): Lobby, getInstance()*}}
 * @constructor
 */
export function LobbyFactory({gameName, roomFactory, roomFee, ruleNormalize = (rule) => rule}) {

  const redisClient = redis.createClient({host: config.get('redis.host')})

  let instance = null;

  return class Lobby {
    static getInstance() {
      if (!instance) {
        instance = new Lobby();
      }
      return instance;
    }

    constructor() {
      this.rooms = new Map();
      this.playerRoomTable = new Map();
      this.roomRegister = new RoomRegister(redisClient)
    }

    getAvailableRoom(playerId, ruleType = 0) {
      let found = null;
      for (const kv of this.rooms) {
        const room = kv[1];
        if (!room.isFull() &&
          room.isPublic &&
          room.game.rule.ruleType === ruleType
        ) {
          found = room;
          break;
        }
      }
      if (found) {
        return found;
      }
      const ret = this.createRoom(true, {
        ruleType,
        share: true,
        isPublic: true,
        difen: 100,
        base: 3
      });
      ret.ownerId = playerId;
      return ret;
    }

    hasRoom(id) {
      return Boolean(this.rooms.get(id));
    }

    getRoom(id) {
      if (id) {
        const room = this.rooms.get(id);
        return room;
      }

      return null;
    }

    async getClubOwner(clubId) {
      const club = await Club.findOne({_id: clubId}).populate('owner')
      if (!club) {
        return
      }
      return club.owner;
    }

    async getClubRooms(clubId) {
      let clubRooms = [];
      const roomNumbers = await redisClient.smembersAsync('clubRoom:' + clubId)

      const roomInfoKeys = roomNumbers.map(num => 'room:info:' + num)

      let roomDatas = []
      if (roomInfoKeys.length > 0) {
        roomDatas = await redisClient.mgetAsync(roomInfoKeys)
      }

      for (const roomData of roomDatas) {
        const roomInfo = JSON.parse(roomData)
        if (roomInfo) {
          const rule = roomInfo.gameRule || 'err';
          const roomNum = roomInfo._id || 'err';
          const roomCreator = roomInfo.creatorName || 'err';
          const playerOnline = roomInfo.players.filter(x => x).length + roomInfo.disconnected.length
          const juIndex = roomInfo.game.juIndex

          clubRooms.push({roomNum, roomCreator, rule, playerOnline, juIndex});
        }
      }

      return clubRooms.sort((x, y) => {
        if (Math.max(x.playerOnline, y.playerOnline) < 4) {
          return y.playerOnline - x.playerOnline
        } else {
          return x.playerOnline - y.playerOnline
        }

      })
    }

    /**
     * @param roomNumber
     * @returns {Promise<{clubShortId:number,clubMode:boolean}>}
     */
    async getRoomInfo(roomNumber) {
      const roomData = await redisClient.getAsync('room:info:' + roomNumber)
      if (!roomData) {
        return {};
      }
      const roomInfo = JSON.parse(roomData)
      return roomInfo;
    }

    async createRoom(isPublic = false, roomId, rule = {}) {
      let newRule = Object.assign({}, rule, {isPublic})

      const room = await roomFactory(roomId, await ruleNormalize(newRule))
      this.listenRoom(room)
      redisClient.sadd('room', roomId)
      return room;
    }

    createTournamentRoom(roomId, rule, playerScore, reporter) {
      const room = new TournamentRoom(rule, playerScore, reporter)
      this.listenRoom(room)
      redisClient.sadd('room', roomId)
      return room;
    }

    async createBattleRoom(roomId, rule, playerScore) {
      const room = new BattleRoom(await ruleNormalize({...rule, isPublic: true}), playerScore)
      room._id = roomId
      this.listenRoom(room)
      redisClient.sadd('room', roomId)
      return room;
    }

    async createClubRoom(isPublic = false, roomId, rule = {}, clubId, clubOwnerPlayer) {
      let newRule = Object.assign({}, rule, {isPublic})

      const room = await roomFactory(roomId, newRule)
      await room.setClub(clubId, clubOwnerPlayer);
      this.listenRoom(room)
      this.listenClubRoom(room)
      redisClient.sadd('clubRoom:' + clubId, roomId)
      return room;
    }

    listenClubRoom(room) {
      room.on('empty', async () => {
        const clubId = room.clubId
        await redisClient.sremAsync('clubRoom:' + clubId, room._id)
        this.clubBroadcaster && this.clubBroadcaster.broadcast(clubId)
      })

      room.on('join', async () => {
        const clubId = room.clubId
        const current = room.players.filter(x => x).length + room.disconnected.length
        this.clubBroadcaster && this.clubBroadcaster.updateClubRoomInfo(clubId, {
          roomNum: room._id,
          capacity: room.capacity, current
        })
      })

      room.on('leave', async () => {
        const clubId = room.clubId

        const current = room.players.filter(x => x).length + room.disconnected.length

        this.clubBroadcaster && this.clubBroadcaster.updateClubRoomInfo(clubId, {
          roomNum: room._id,
          capacity: room.capacity, current
        })
      })
    }

    listenRoom(room) {
      room.on('empty', async (disconnectedPlayerIds = []) => {
        disconnectedPlayerIds.forEach(id => {
          this.roomRegister.removePlayerFromGameRoom(id, gameName)
            .catch(error => {
              console.error('removePlayerFromGameRoom', id, gameName, error)
            })
        })
      })
    }

    clearDisConnectedPlayer(playerId) {
      this.playerRoomTable.delete(playerId);
    }

    /**
     * @param {string} playerId
     * @returns {Promise<*>}
     */
    async getDisconnectedRoom(playerId) {
      const roomNumber = await this.roomRegister.roomNumber(playerId, gameName)

      const roomExist = await redisClient.getAsync(this.roomKey(roomNumber))
      if (roomExist) {
        return roomNumber;
      }
    }

    /**
     * @param {string} roomId
     * @returns {Promise<boolean>}
     */
    async isRoomExists(roomId) {
      const roomExist = await redisClient.getAsync(this.roomKey(roomId))
      return roomExist
    }

    roomKey(roomNum) {
      return `room:${roomNum}`
    }

    roomFee(rule) {
      return roomFee(rule)
    }

  }
}

export default LobbyFactory;
